/*

 util工具类

 根据实际情况自定义的使用工具封装类

*/

const childProcess = require('child_process');
const request = require('request')
const crypto = require('crypto')
const iconv = require('iconv-lite')

class common {

	constructor(_extends) {
		Object.assign(this, _extends)
	}

	resolve(data = 0) {
		return Promise.resolve(data)
	}

	reject(code, msg) {
		return Promise.reject({code, msg})
	}

	isNum(obj) {
		return /^\d+$/.test(obj)
	}

	//判断是否字符串
	isString(obj) {
		return Object.prototype.toString.call(obj) === '[object String]'
	}

	//判断是否数组
	isArray(obj) {
		return Object.prototype.toString.call(obj) === '[object Array]'
	}

	//判断是否json
	isJson(val) {
		try {
			let jsonData = JSON.parse(val)
			return jsonData
		}
		catch(err){
			return false
		}
	}

	isExists(obj) {
		return typeof(obj) != 'undefined'
	}

	//打印边框消息
	borderMsg(...msg) {
		this.borderLog('log', ...msg)
	}

	//打印边框错误消息
	borderErr(...msg) {
		this.borderLog('error', ...msg)
		console.trace()
	}

	borderLog(type, ...msg) {
		let str = '╔'
		let max = 0
		for(let m of msg) 
			max = m.length > max ? m.length : max
		for(let i = 0;i < max + 2;i++) {
			str += '═'
		}
		str += '╗\n'
		for(let j = 0;j < msg.length;j++) {
			str += '║ '
			let s = msg[j]
			while(s.length < max) s += ' '
			str += s
			str += ' ║\n'
		}
		str += '╚'
		for(let i = 0;i < max + 2;i++) {
			str += '═'
		}
		str += '╝'
		console[type](str)
	}

	//异步写入log文件
	writeLog(type, msg = '', notPrint = true, error) {
		try {
			if(error) {
				if(error.code)
					msg += ` [${error.code} ${error.msg}]`
				else
					msg += error.toString()
			}
			let time = this.timestamp2str(this.timestamp(), true, true, true, true, true, true, '/')
			let log = `${time} [${type}]: ${msg}\r\n`
			this.fs.outputFile(this.config.frame.logFile, log, { flag: 'a' }, (err) => {
				if(err) {
					console.error('CXXYAPI服务写入日志失败', err)
					this.alertMsg('写入日志失败', `GTAPI服务的日志机制出错无法写入日志内容：[${type}]${msg}`, err)
				}
			})
			if(!notPrint) {
				if(type == 'error') {
					console.trace()
					console.error(`${log} error is wrote to ${process.cwd()}/error.log`)
				}
				else
					console.log(`${log} log is wrote`)
			}
		}
		catch(err) {
			console.error('log failed:', err)
		}
	}

	//同步写入log文件
	writeLogSync(type, msg, notPrint = true) {
		return new Promise( async (resolve, reject) => {
			let time = this.timestamp2str(this.timestamp(), true, true, true, true, true, true, '/')
			let log = `${time} [${type}]: ${msg}\r\n`
			fs.outputFile(this.config.frame.logFile, log, {
				flag: 'a'
			}, (err) => {
				if(notPrint)
					return
				if(err) {
					console.error('log failed:', err)
					reject({
						code: '-1064',
						msg: 'write log failed'
					})
				}
				if(type == 'error') {
					console.trace()
					console.error(`${log} error is wrote to ${process.cwd()}/error.log`)
				}
				else
					console.log(`${log} log is wrote`)
				resolve()
			})
		})
	}

	alertMsg(title = '', content = '', error) {
		const alertUrl = this.config.frame.alertUrl
		if(error) {
			if(error.code)
				content += ` [${error.code} ${error.msg}]`
			else
				content += error.toString()
		}
		console.trace()
		console.error(title, content, error)
		this.reqPost({
			url: alertUrl,
			headers: {
				'content-type': 'application/x-www-form-urlencoded'
			},
			data: {
				text: title,
				desp: content
			}
		}).catch((err) => {
			this.writeLog('error', 'send alert to master failed ' + err.toString(), false).catch((_err) => {
				console.error('send alert to master  and write log failed', _err)
			})
		})
	}

	async getAccessToken(appName) {
		const result = await this.redis.accessToken.get(appName, true)
		if(!result)
			return this.reject('-2032', 'app token not found')
		return result
	}

	//执行shell命令
	execShell(command) {
		return new Promise( async  (resolve, reject) => {
			//开启子线程执行命令并返回结果
			childProcess.exec(command, (err, stdOut, stdErr) => {
				if(err || stdErr) {
					console.error(err, stdErr)
					//执行出现错误
					reject({
						code: '-1060',
						msg: 'exec shell command failed'
					})
				}
				else {
					//执行完毕并返回输出内容
					resolve(stdOut)
				}
			})
		})
	}
	
	//生成固定长度随机字符串
	async createRandomStr(len) {
		let randomStr = ''
		try {
			randomStr = await this.execShell(`head -n 80 /dev/urandom | tr -dc A-Za-z0-9 | head -c ${len}`)
			return this.resolve(randomStr)
		}
		catch(err) {
			console.log('this system not support create random string command', err)
			try {
				randomStr = ''
				let material = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
				for(let i = 0;i < len;i++) {
					//添加在素材字符串随机找到的字符
					randomStr += material[parseInt(Math.random() * 61)];
				}
				return this.resolve(randomStr)
			}
			catch(err) {
				console.error(err)
				return this.reject('-1059', 'create random string failed')
			}
		}
	}

	//对字符串进行md5计算
	md5(str) {
		try {
			let cryptStr = crypto.createHash('md5').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1058', msg:'crypto data failed'}
		}
	}

	//对字符串进行sha1计算
	sha1(str) {
		try {
			let cryptStr = crypto.createHash('sha1').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1055', msg:'crypto data failed'}
		}
	}

	//对字符串进行sha256计算
	sha256(str) {
		try {
			let cryptStr = crypto.createHash('sha256').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1056', msg:'crypto data failed'}
		}
	}

	//对字符串进行sha512计算
	sha512(str) {
		try {
			let cryptStr = crypto.createHash('sha512').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1057', msg:'crypto data failed'}
		}
	}

	js2xml(obj) {
		try {
			return xmlBuilder.buildObject(obj)
		}
		catch(err) {
			return {code: '-1024', msg: 'convert object to xml failed'}
		}
	}

	//xml数据转json数据
	xml2js(xmlStr) {
		return new Promise( async (resolve, reject) => {
			try {
				xmlParser.parseString(xmlStr, (err, obj) => {
					if(err) {
						console.error(err)
						reject({
							code: '-1054',
							msg: 'convert xml to object failed'
						})
					}
					else {
						resolve(obj)
					}
				})
			}
			catch(err) {
				console.error(err)
				reject({
					code: '-1054',
					msg: 'convert xml to object failed'
				})
			}
		})
	}

	getSpaceTime(expire) {
		try {
			expire = expire < 300 ? 300000 : expire * 1000
			let nowTime = Date.now()
			let startDate = new Date(nowTime)
			let endDate = new Date((nowTime + expire))
			let startTimeStr = ''
			let endTimeStr = ''
			let data = {
				start: {
					year: startDate.getFullYear(),
					month: startDate.getMonth() + 1,
					day: startDate.getDate(),
					hours: startDate.getHours(),
					min: startDate.getMinutes(),
					second: startDate.getSeconds()
				},
				end: {
					year: endDate.getFullYear(),
					month: endDate.getMonth() + 1,
					day: endDate.getDate(),
					hours: endDate.getHours(),
					min: endDate.getMinutes(),
					second: startDate.getSeconds()
				}
			}
			for(let key in data) {
				for(let _key in data[key]) {
					if(_key != 'year' && data[key][_key] < 10) {
						data[key][_key] = `0${data[key][_key]}`
					}
					if(key == 'start') {
						startTimeStr += data[key][_key].toString()
					}
					if(key == 'end') {
						endTimeStr += data[key][_key].toString()
					}
				}
			}
			return {
				startTime: startTimeStr,
				endTime: endTimeStr
			}
		}
		catch(err) {
			console.error(err)
			throw {code:'-2051', msg:'build space time failed'}
		}
	}

	//判断路径是否存在
	async pathExists(path) {
		try {
			const exists = await this.fs.pathExists(path)
			return this.resolve(exists)
		}
		catch(err) {
			return this.reject('-1052', 'judge path exists failed')
		}
	}

	//将时间戳转字符串日期
	 timestamp2str(timestamp, year = true, month = true, day = true, hours, min, second, split = '-') {
		try {
			let date = new Date(timestamp.toString().length == 10 ? timestamp * 1000 : timestamp)
			let _year = `${year ? (date.getFullYear() > 9 ? date.getFullYear() : '0' + date.getFullYear()) + split : ''}`
			let _month = `${month ? (date.getMonth() + 1 > 9 ? date.getMonth() + 1 : '0' + (date.getMonth() + 1)) + split : ''}`
			let _day = `${day ? (date.getDate() > 9 ? date.getDate() : '0' + date.getDate()) : ''}`
			let _hours = `${hours ? ' ' + (date.getHours() > 9 ? date.getHours() : '0' + date.getHours()) + ':' : ''}`
			let _min = `${min ? (date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()) + ':' : ''}`
			let _second = `${second ? (date.getSeconds() > 9 ? date.getSeconds() : '0' + date.getSeconds()) : ''}`
			return `${_year}${_month}${_day}${_hours}${_min}${_second}`
		}
		catch(err) {
			console.error(err)
			throw {code:'-1017', msg:'timestamp convert to date string failed'}
		}
	}

	//发起GET请求
	async reqGet(params) {
		try {
			let {url, data, headers, cookie, json, ignoreAuth, getCookie, getStatus, getContentInfo, getBuffer } = params
			json = typeof(json) == 'undefined' ? true : json
			let reqUri = ''
			if(data) {
				reqUri = '?'
				for(let key in data) {
					reqUri += `${key}=${data[key]}&`
				}
				url += this.rtrim(reqUri, '&')
			}
			let jar = cookie ? request.jar() : false
			if(cookie)
				for(let url in cookie)
					jar.setCookie(request.cookie(await this.cookieStringify(cookie[url])), url)
			console.log(url, {
				url,
				method: 'get',
				headers: headers || {},
				json,
				encoding: getBuffer ? null : undefined,
				rejectUnauthorized: !ignoreAuth,
				jar
			})
			let result = await request({
				url,
				method: 'get',
				headers: headers || {},
				json,
				encoding: getBuffer ? null : undefined,
				rejectUnauthorized: !ignoreAuth,
				jar,
				timeout: 60000
			})
			let extData = {}
			if(getCookie) {
				//console.log(jar.getCookies('120.86.68.212'))
				Object.assign(extData, {
					cookie: await this.cookieParseByArr(result.headers['set-cookie'])
				})
			}
			if(getContentInfo) {
				Object.assign(extData, {
					contentType: result.headers['content-type'],
					contentLen: result.headers['content-length']
				})
			}
			if(getStatus) {
				Object.assign(extData, {
					status: {
						code: result.statusCode,
						msg: result.statusMessage
					}
				})
			}
			return this.resolve(!getCookie && !getContentInfo && !getStatus ? result.body : Object.assign(extData, {data: result.body}))
		}
		catch(err) {
			console.error(err)
			return this.reject( '-1019', 'send GET request failed')
		}
	}

	//发起POST请求
	async reqPost(params) {
		try {
			let {url, data, headers, cookie, followCookie, notFollow, getData, json, ignoreAuth, getCookie, getContentInfo, getStatus, getBuffer} = params
			let reqUri = ''
			data = data || {}
			json = typeof(json) == 'undefined' ? true : json
			if(getData) {
				reqUri = '?'
				for(let key in getData) {
					reqUri += `${key}=${getData[key]}&`
				}
				url += this.rtrim(reqUri, '&')
			}
			let jar = cookie ? request.jar() : false
			if(cookie)
				for(let url in cookie)
					jar.setCookie(request.cookie(await this.cookieStringify(cookie[url])), url)
			let requestData = {
				url,
				method: 'post',
				headers: headers || {'content-type': 'application/json', charset: 'UTF-8'},
				json,
				encoding: getBuffer ? null : undefined,
				rejectUnauthorized: !ignoreAuth,
				jar: followCookie || jar,
				followRedirect: !notFollow
			}
			requestData.headers.charset = 'UTF-8'
			switch(requestData.headers['content-type'] || requestData.headers['Content-Type']) {
				case 'application/x-www-form-urlencoded':
					requestData.form = data
				break
				case 'application/json':
				case 'application/xml':
					requestData.body = data
				break
			}
			console.log(requestData)
			let result = await request(requestData)
			let extData = {}
			if(getCookie) {
				Object.assign(extData, {
					cookie: await this.cookieParseByArr(result.headers['set-cookie'])
				})
			}
			if(getContentInfo) {
				Object.assign(extData, {
					contentType: result.headers['content-type'],
					contentLen: result.headers['content-length']
				})
			}
			if(getStatus) {
				Object.assign(extData, {
					status: {
						code: result.statusCode,
						msg: result.statusMessage
					}
				})
			}
			return this.resolve(!getCookie && !getContentInfo && !getStatus ? result.body : Object.assign(extData, {data: result.body}))
		}
		catch(err) {
			console.error(err)
			return this.reject('-1020', 'send POST request failed')
		}
	}

	//cookie字符串转换为对象
	cookieParseByStr(cookieStr) {
		try {
			let obj = {}
			let temp = cookieStr.split(';')
			for(let data of temp) {
				data = this.trim(data)
				let kv = data.split('=')
				if(!kv || !kv[0])
					throw {code:'-1040', msg:'parse cookie str to object failed'}
				if(!kv[1])
					kv[1] = ''
				obj[this.trim(kv[0])] = kv[1]
			}
			return obj
		}
		catch(err) {
			console.error(err)
			throw {code:'-1040', msg:'parse cookie str to object failed'}
		}
	}

	//转换cookie字符串数组为对象数组
	cookieParseByArr(cookieArr = []) {
		try {
			let arr = []
			for(let c of cookieArr) {
				let temp = this.cookieParseByStr(c)
				arr.push(temp)
			}
			return arr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1042', msg:'parse cookie array to object array failed'}
		}
	}

	//对象转换为cookie字符串
	cookieStringify(cookieObj) {
		try {
			let str = ''
			for(let k in cookieObj) {
				str += `; ${k}=${cookieObj[k]}`
			}
			str = this.ltrim(str, '; ')
			return str
		}
		catch(err) {
			console.error(err)
			throw {code:'-1041', msg:'stringify object to cookie str failed'}
		}
	}

	trim(str, symbol = '\\s') {
		if(!str)
			return ''
		return str.replace(new RegExp(`(^${symbol}*)|(${symbol}*$)`, 'g'), '')
	}

	ltrim(str, symbol = '\\s') {
		if(!str)
			return ''
		return str.replace(new RegExp(`(^${symbol}*)`, 'g'), '')
	}

	rtrim(str, symbol = '\\s') {
		if(!str)
			return ''
		return str.replace(new RegExp(`(${symbol}*$)`, 'g'), '')
	}

	//获得当前时间戳
	timestamp() {
		return parseInt(Date.now() / 1000)
	}

	dateStr2timestamp(dateStr) {
		try {
			if(!dateStr || (dateStr.length != 8 && dateStr.length != 14) || isNaN(dateStr))
				throw {code:'-1044', msg:'date string invalid'}
			let date = parseInt(Date.parse(dateStr.length == 8 ? `${dateStr.substring(0, 4)}/${dateStr.substring(4, 6)}/${dateStr.substring(6, 8)}` : `${dateStr.substring(0, 4)}/${dateStr.substring(4, 6)}/${dateStr.substring(6, 8)} ${dateStr.substring(8, 10)}:${dateStr.substring(10, 12)}:${dateStr.substring(12, 14)}`) / 1000)
			return date
		}
		catch(err) {
			console.error(err)
			throw {code: '-1018', msg: 'date string convert to timestamp failed'}
		}
	}

	/**
	 *
	 * [private] 获取用户session数据
	 *
	 * @param [string] session  用户session
	 *
	 * @return [object] 用户session数据
	 *
	 */

	async getSession(session) {
		try {
			return await this.redis.session.get(session, true)
		}
		catch(err) {
			console.error(err)
			return this.reject('-1063', 'get session failed')
		}
	}

	/**
	 *
	 * [private] 修改用户session类型
	 *
	 * @param [string] session  用户session
	 * @param [string] type 需要更改的用户类型
	 *
	 * @return [number] 1
	 *
	 */

	async updateSessionType(session, type) {
		try {
			let sessionData = await this.getSession(session)
			if(sessionData.type == type) {
				console.log (`user ${session} type is up-to-date`)
				return
			}
			sessionData.type = type
			await this.redis.session.set(session, sessionData, this.config.frame.sessionExpires[sessionData.type || 'all'] || 86400)
			console.log(`user ${session} type update to ${type}`)
		}
		catch(err) {
			console.error(err)
			return this.reject('-1062', 'update session type failed')
		}
	}

	/**
	 *
	 * 修改用户session的data数据
	 *
	 * @param [string] session  用户session
	 * @param [string] data 需要更改的数据
	 *
	 * @return [number] 1
	 *
	 */

	async updateSessionData(session, data = {}) {
		try {
			if(!session)
				return this.reject('-1021', 'update session data session not found')
			let sessionData = await this.getSession(session)
			Object.assign(sessionData.data, data)
			await this.redis.session.set(session, sessionData, this.config.frame.sessionExpires[sessionData.type || 'all'] || 86400)
			console.log(`user ${session} data update to`, data)
		}
		catch(err) {
			console.error(err)
			return this.reject('-1061', 'update session data failed')
		}
	}

}

//编码转换模块
common.prototype.iconv = iconv

module.exports = common